package com.mstai.boc;

import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*
import java.text.DecimalFormat

interface Event {
    fun isExit() : Boolean
    fun clone(): Event
}


class IntEvent(private val value: Int) : Event {
    override fun isExit(): Boolean {
        return value == -1
    }

    override fun clone(): Event {
        return IntEvent(value)
    }
}

class StrEvent(private val value: String) : Event {
    private lateinit var chars : CharArray 
    override fun isExit(): Boolean {
        return value == "exit"
    }
    override fun clone(): Event {
        if (!this::chars.isInitialized) {
            chars = value.toCharArray()
        }
        return StrEvent(String(chars))
    }
}

class CheapStrEvent(private val value: String) : Event {
    override fun isExit(): Boolean {
        return value == "exit"
    }
    override fun clone(): Event {
        return CheapStrEvent(value)
    }
}

suspend fun worker(queue: Channel<Event>, events: Int, done: Channel<Int>) {
    var n = 0
    repeat(events) {
        val event = queue.receive()
        if (!event.isExit()) {
            n += 1
        }
    }
    done.send(n)
}

suspend fun dispatch(workers: Int, events: Int, queueSize: Int, done: Channel<Int>, event: Event) = coroutineScope{
    repeat(workers) {
        val queue = Channel<Event>(queueSize)

        launch(Dispatchers.Default) {
            worker(queue, events, done)
        }

        launch(Dispatchers.Default) {
            repeat(events) {
                queue.send(event.clone())
            }
        }
    }
}

fun makeInitEvent(eventType: Int, eventSize: Int) : Event {
    return when (eventType) {
        0 -> {
            IntEvent(eventSize)
        }
        1 -> {
            CheapStrEvent("A".repeat(eventSize))
        }
        2 -> {
            CheapStrEvent("A".repeat(eventSize))
        }
        3 -> {
            StrEvent("A".repeat(eventSize))
        }
        else -> {
            throw Exception("event type is invalid $eventType ")
        }
    }
}

fun eventTypeName(eventType: Int) : String {
    return when (eventType) {
        0 -> "int"
        1 -> "str"
        2 -> "str_ptr"
        3 -> "str_clone"
        else -> "unknown"
    }
}

fun main(args: Array<String>) = runBlocking { // this: CoroutineScope

    val parser = ArgParser("bockt")

    val workers by parser.option(ArgType.Int, shortName = "w").default(100)
    val events by parser.option(ArgType.Int, shortName = "e").default(100)
    val queueSize by parser.option(ArgType.Int, shortName = "q").default(16)
    val eventType by parser.option(ArgType.Int, shortName = "t").default(0)
    val eventSize by parser.option(ArgType.Int, shortName = "s").default(64)
    val verbose by parser.option(ArgType.Boolean, shortName = "v").default(false)
    val cvsFormat by parser.option(ArgType.Boolean, shortName = "c").default(false)
    val cpuProfile by parser.option(ArgType.String, fullName = "cpuprofile")

    parser.parse(args)




    val warmUpDone = Channel<Int>(1000)
    // warmup
    launch {
        dispatch(1000, 1000, 100, warmUpDone, makeInitEvent(eventType, eventSize))
    }

    repeat(1000) {
        warmUpDone.receive()
    }
    warmUpDone.close()



    val done = Channel<Int>(workers)
    val t1 = System.nanoTime()

    launch {
        dispatch(workers, events, queueSize, done, makeInitEvent(eventType, eventSize))
    }

    var sn = 0
    repeat(workers) {
        sn += done.receive()
    }

    val t2 = System.nanoTime()

    val totalEvents = workers * events
    val time = (t2 - t1).toDouble() / 1e9
    val speed =  (totalEvents.toDouble()  / time).toLong()

    if (verbose) {
        println("total events $totalEvents done events $sn")
    }

    val fmt = DecimalFormat("#.###")
    println("kotlin,${eventTypeName(eventType)},${workers},${events},${fmt.format(time)},${speed}")

}
